local zylib = require("zylib")
local bit = require("bit")
require("pack")

--[[
# HEX 文件解析
## 打开Hex文件，看到类似如下文本：
:1000D000E70100088D050008E7010008E7010008B6
（1）起始标识（:）
（2）第1字节（10） ，表示本行数据的长度（0x10）
（3）第2、3字节（00D0），表示本行数据的起始地址（0x00D0）
（4）第4字节（00）表示数据类型，包括：0x00、0x01、0x02、0x03、0x04、0x05
	1. "00" Data Record：数据记录，HEX 文件的大部分记录都是数据记录
	2. "01" End of File Record: 结束文件记录
	3. "02" Extended Segment Address Record: 用来标识扩展段地址的记录
	4. "03" Start Segment Address Record: 开始段地址记录
	5. "04" Extended Linear Address Record: 用来标识扩展线性地址的记录
	6. "05" Start Linear Address Record: 开始线性地址记录
（5）数据（E70100088D050008E7010008E7010008），共0x10 个字节
（6）最后一个数据（B6），表示校验和。注：计算 0xB6 前所有16进制数的累加和，不计进位（即：低位单字节），检验和 = 0x100 - 累加和。

## 关于 "04" Extended Linear Address Record: 用来标识扩展线性地址的记录
```
:020000041100E9
:102100000100000000900000204500000080FF1F3B
```
该行记录的是一个拓展地址(0x08 0x00 是地址信息，用法是将该地址（0x0800<<16) 后作为基地址。并且表示在下一个04类型行出现之前都要使用该地址

## 关于 "05" Start Linear Address Record: 开始线性地址记录
```
:0400000511002100C5
```
- 一般情况下，其实就是程序主函数入口地址，可以在 map 文件中看到此地址对应的函数名(如 app_main() 函数地址)
- 烧录器在解析到这条记录后，就知道如果要运行程序的话，要跳转到哪里去运行了！
]]

-- 分析 hex 文件的一行
local function hex_parse_line(hex, line)
    -- 取得各个字段数据
    local ps, pe, len, offset_h, offset_l, type, data, checksum =
        line:find(":(%x%x)(%x%x)(%x%x)(%x%x)(%x*)(%x%x)", 1, false)
    if pe == nil then
        error(string.format("@%d: Invalid format !\r\n\t%s", hex.lcount, line))
    end

    -- 字段由字符串转换为数字
    len 	 = tonumber("0x" .. len     )
    offset_h = tonumber("0x" .. offset_h)
    offset_l = tonumber("0x" .. offset_l)
    type 	 = tonumber("0x" .. type    )
    checksum = tonumber("0x" .. checksum)

    if (#data ~= len*2) then
        error(string.format("@%d: Incorrect length and data !\r\n\t%s", hex.lcount, line))
    end

    -- 计算 checksum
    local cks = len + offset_h + offset_l + type;

    -- 将 hex 字符串转换成二进制串
    local bin = ""
    for i=1, 2*len, 2 do
        local byte = data:sub(i, i+1)
        byte = tonumber("0x" .. byte)
        cks = cks + byte
        bin = bin .. string.char(byte)
    end

    -- 检查 checksum
    cks = (256 - (cks % 256))%256

    if (cks ~= checksum) then
        error(string.format("@%d: Incorrect checksum '%02X' (should be '%02X') !\r\n\t%s", hex.lcount, checksum, cks, line));
    end

    local offset = offset_h * 0x100 + offset_l

    return len, offset, type, bin
end

-- 从二进制串中指定位置，解析出一个(大端在前的)无符号整数
local function unpackUnsigned(bytes, len, offset)
	local _, val;

	if len == 1 then
		_, val = string.unpack(bytes, "b", offset);
	elseif len == 2 then
		_, val = string.unpack(bytes, ">H", offset);
	elseif len == 4 then
		_, val = string.unpack(bytes, ">I", offset);
	elseif len == 8 then
		_, val = string.unpack(bytes, ">L", offset);
	else
		error("无法解析大于 64 位的整数！len = " .. len)
	end

    return val
end

-- 创建一个新的段
local function newSegment(start)
	return {
		start = start; -- 段地址:2字节
		offset = nil;  -- 偏移地址:2字节
		data = nil;    -- 数据为空
	};
end

-- 添加数据段到 hex 结构
local function addSegment(hex, segment)
	if (not segment) or (not segment.data) or (#segment.data < 1) then
		print("警告：数据段为空！跳过！");
		return;
	end

	-- 保存到 hex 结构
	table.insert(hex.segments, segment)
	print(string.format("@%d: add segment: start=%08X, size=%05X/%d bytes", hex.lcount, segment.start*0x10000 + segment.offset, #segment.data, #segment.data));
end

-- 分析 hex 文件，并加载它
-- @param hexFilename hex 文件名
-- @return hex 数据结构 hex = {
-- 	filename = hexFilename; -- 文件名
-- 	lcount = 0;             -- 当前解析到第几行
-- 	segments = {};          -- 数据段列表
-- 	linearAddresses = {};   -- 线性地址列表
-- }
local function hex_parse(hexFilename)
	local hexFile, err = io.open(hexFilename, "r")
	if (hexFile == nil) then
		print(err)
		return nil
	end

	print("\r\nBegin to parse hex file : " .. hexFilename  .. " ...")

	-- 新建一个 hex 数据结构
	local hex = {
		filename = hexFilename; -- 文件名
		lcount = 0;             -- 当前解析到第几行
		segments = {};          -- 数据段列表
		linearAddresses = {};   -- 线性地址列表
	}
	-- 当前数据段结构
    local segment = nil;

	repeat
		-- 读取一行数据
		local line = hexFile:read("*l");
		if (line == nil) then break; end
        hex.lcount = hex.lcount + 1;

        local len, offset, type, data = hex_parse_line(hex, line)
        if type == 0 then
			-- 是数据记录
			if segment == nil then
				print(string.format("@%d: 警告：当前数据段没有开始段地址记录！默认段地址为 0x00000000！\r\n\t%s", hex.lcount, line));
				segment = newSegment(0);
			end
			if segment.offset == nil then
				-- 是当前数据段的第一条数据
				segment.offset = offset;
				segment.data = data;
			elseif offset == (segment.offset + #segment.data) then
				-- 是当前数据段的后续数据，添加到当前段数据后面
				segment.data = segment.data .. data
			else
				print(string.format("@%d: 警告：不连续的段数据！认为是新的数据段开始！默认段地址为 0x00000000！\r\n\t%s", hex.lcount, line));
				-- 保存当前数据段
				addSegment(hex, segment);
				-- 创建一个新的段
				segment = newSegment(0);
				-- 是当前数据段的第一条数据
				segment.offset = offset;
				segment.data = data;
			end
        elseif type == 1 then
			-- 是结束文件记录
			break;
        elseif type == 3 then
			-- 是扩展地址段记录
			error("不知如何处理 '扩展地址段记录'：" .. line);
        elseif type == 4 then
			-- 是开始段地址记录
			if segment ~= nil then
				-- 保存当前数据段
				addSegment(hex, segment);
			end
			-- 检查地址合法性
			if len ~= 2 then error(string.format("@%d: 开始地址记录中，地址必须为 2 字节！\r\n\t%s", hex.lcount, line)); end
			local start = unpackUnsigned(data, 2);
			-- 创建一个新的段
			segment = newSegment(start);
        elseif type == 5 then
			-- 是线性地址记录
			local linearAddress = unpackUnsigned(data, len);
			print(string.format("@%d: linearAddress = %08X", hex.lcount, linearAddress));
			-- 保存线性地址
            table.insert(hex.linearAddresses, linearAddress);
        else
			error(string.format("@%d: 无法识别的记录类型！\r\n\t%s", hex.lcount, line));
        end
	until (false)

	-- 保存当前数据段(最后一个)
	addSegment(hex, segment);

	hexFile:close()

	print(string.format("*** Total %d lines, %d segments !", hex.lcount, #hex.segments));
	return hex
end

-- 修改指定段内的数据
local function hex_modify(hex, address, data)
    local find = false
    for i, segment in ipairs(hex) do
        if address >= segment.offset and address <= segment.offset + #segment.data then
            if address + #data > segment.offset + #segment.data then
                print("要修改的数据超出数据段长度！")
            end
            find = true
            local offset = (address - segment.offset);
            local d1 = string.sub(segment.data, 1, offset)
            local d2 = string.sub(segment.data, offset + #data + 1)
            segment.data = d1 .. data .. d2
        end
    end
    if not find then
        print("Can't find segment !")
    end
end

-- 构建行
local function hex_build_line(offset, type, data)
    -- 构建二进制行
    local line = string.pack("b>Hb", #data, offset, type) .. data

    -- 计算校验和
    local cks = 0
    for i=1, #line do
        cks = cks + line:byte(i)
    end
    cks = (256 - (cks % 256))%256
    line = line .. string.char(cks)

    -- bin转hex
    return ":" .. zylib.bin2Hex(line)
end

local function hex_save(hex, hexFilename, lineWidth)
	local hexFile, err = io.open(hexFilename, "wb")
	if (not hexFile) then
		print(err)
		return
	end

    for i, segment in ipairs(hex.segments) do
        local offset = segment.offset
        if offset >= 0x10000 then
        end

        -- 计算本段的行数
        local lines = math.floor(#segment.data / lineWidth)
        if #segment.data % lineWidth > 0 then lines = lines + 1 end

        for l=1, lines do
            local sb = offset - segment.offset + 1
            local len = #segment.data - sb + 1
            if len > lineWidth then len = lineWidth end
            local se = sb + len - 1
            local data = string.sub(segment.data, sb, se)

            local str = hex_build_line(offset, segment.type, data)

            offset = offset + #data
            hexFile:write(str)
            hexFile:write("\r\n")
        end
    end

    hexFile:close()
end

return {
	parse = hex_parse;
	modify = hex_modify;
	save = hex_save;
}
